import * as crypto from "crypto";

/**
 * <p>
 * RSA公钥/私钥/签名工具包
 * </p>
 * <p>
 * 罗纳德·李维斯特（Ron [R]ivest）、阿迪·萨莫尔（Adi [S]hamir）和伦纳德·阿德曼（Leonard [A]dleman）
 * </p>
 * <p>
 * 字符串格式的密钥在未在特殊说明情况下都为BASE64编码格式<br/>
 * 由于非对称加密速度极其缓慢，一般文件不使用它来加密而是使用对称加密，<br/>
 * 非对称加密算法可以用来对对称加密的密钥加密，这样保证密钥的安全也就保证了数据的安全
 * </p>
 */
export class RSAUtils {
  /**
   * 加密算法RSA
   */
  public static readonly KEY_ALGORITHM = "RSA";

  /**
   * 加密算法RSA
   */
  public static readonly KEY_ECB_ALGORITHM = "RSA-OAEP";

  /**
   * 签名算法
   */
  public static readonly SIGNATURE_ALGORITHM = "RSA-SHA1";

  /**
   * 获取公钥的key
   */
  public static readonly PUBLIC_KEY = "RSAPublicKey";

  /**
   * 获取私钥的key
   */
  public static readonly PRIVATE_KEY = "RSAPrivateKey";

  /**
   * RSA最大加密明文大小
   */
  private static readonly MAX_ENCRYPT_BLOCK = 117;

  /**
   * RSA最大解密密文大小
   */
  private static readonly MAX_DECRYPT_BLOCK = 128;

  /**
   * <p>
   * 生成密钥对(公钥和私钥)
   * </p>
   *
   * @returns 包含公钥和私钥的对象
   */
  public static async genKeyPair(): Promise<{ [key: string]: string }> {
    const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
      modulusLength: 1024,
      publicKeyEncoding: {
        type: "spki",
        format: "pem",
      },
      privateKeyEncoding: {
        type: "pkcs8",
        format: "pem",
      },
    });
    console.log("publicKey:", publicKey);
    console.log("privateKey:", privateKey);
    // const keyMap: { [key: string]: string } = {};
    const keyMap: { [key: string]: string } = {};
    keyMap[RSAUtils.PUBLIC_KEY] = publicKey;
    keyMap[RSAUtils.PRIVATE_KEY] = privateKey;
    return keyMap;
  }

  //   /**
  //    * <p>
  //    * 用私钥对信息生成数字签名
  //    * </p>
  //    *
  //    * @param data       已加密数据
  //    * @param privateKey 私钥(BASE64编码)
  //    * @returns 签名后的字符串
  //    */
  public static async sign(data: string, privateKey: string): Promise<string> {
    let privateK: crypto.KeyObject;
    // 判断是pkcs1私钥还是pkcs8私钥
    if (privateKey.includes("BEGIN RSA PRIVATE KEY")) {
      // 如果是pkcs1私钥
      privateKey = privateKey
        .replace("-----BEGIN RSA PRIVATE KEY-----", "")
        .replace("-----END RSA PRIVATE KEY-----", "")
        .replace(/\n/g, "");
      const keyBytes = RSAUtils.decode(privateKey);
      privateK = crypto.createPrivateKey({
        key: keyBytes,
        format: "der",
        type: "pkcs1",
      });
    } else {
      // 如果是pkcs8私钥
      privateKey = privateKey
        .replace("-----BEGIN PRIVATE KEY-----", "")
        .replace("-----END PRIVATE KEY-----", "")
        .replace(/\n/g, "");
      const keyBytes = RSAUtils.decode(privateKey);
      privateK = crypto.createPrivateKey({
        key: keyBytes,
        format: "der",
        type: "pkcs8",
      });
    }

    const sign = crypto.createSign(RSAUtils.SIGNATURE_ALGORITHM);
    sign.update(data);
    return sign.sign(privateK, "base64");
  }

  /**
   * <p>
   * 校验数字签名
   * </p>
   *
   * @param data      已加密数据
   * @param publicKey 公钥(BASE64编码)
   * @param sign      数字签名
   * @returns 签名是否有效
   */
  public static async verify(
    data: string,
    publicKey: string,
    sign: string
  ): Promise<boolean> {
    publicKey = publicKey
      .replace("-----BEGIN PUBLIC KEY-----", "")
      .replace("-----END PUBLIC KEY-----", "")
      .replace(/\n/g, "");
    // console.log("publicKey:", publicKey);
    const keyBytes = RSAUtils.decode(publicKey);
    // console.log("keyBytes:", keyBytes);
    const publicK = crypto.createPublicKey({
      key: keyBytes,
      format: "der",
      type: "spki",
    });

    // console.log("publicK:", publicK);
    // console.log("sign:", sign);
    const verify = crypto.createVerify(RSAUtils.SIGNATURE_ALGORITHM);
    // console.log("verify:", verify);
    verify.update(data);
    return verify.verify(publicK, sign, "base64");
  }

  /**
   * <P>
   * 私钥解密
   * </p>
   *
   * @param encryptedData 已加密数据
   * @param privateKey    私钥(BASE64编码)
   * @returns 解密后的数据
   */
  public static async decryptByPrivateKey(
    encryptedData: Buffer,
    privateKey: string
  ): Promise<Buffer> {
    privateKey = privateKey
      .replace("-----BEGIN PRIVATE KEY-----", "")
      .replace("-----END PRIVATE KEY-----", "")
      .replace(/\n/g, "");
    const keyBytes = RSAUtils.decode(privateKey);
    const privateK = crypto.createPrivateKey({
      key: keyBytes,
      format: "der",
      type: "pkcs8",
    });

    const decryptedChunks: Uint8Array[] = [];
    let offset = 0;
    while (offset < encryptedData.length) {
      const chunkSize = Math.min(
        encryptedData.length - offset,
        RSAUtils.MAX_DECRYPT_BLOCK
      );
      const chunk = new Uint8Array(
        encryptedData.subarray(offset, offset + chunkSize)
      );
      const decryptedChunk = crypto.privateDecrypt(
        {
          key: privateK,
          padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        },
        chunk
      );
      decryptedChunks.push(new Uint8Array(decryptedChunk));
      offset += chunkSize;
    }
    return Buffer.concat(decryptedChunks);
  }

  /**
   * <p>
   * 公钥解密
   * </p>
   *
   * @param encryptedData 已加密数据
   * @param publicKey     公钥(BASE64编码)
   * @returns 解密后的数据
   */
  public static async decryptByPublicKey(
    encryptedData: Buffer,
    publicKey: string
  ): Promise<Buffer> {
    publicKey = publicKey
      .replace("-----BEGIN PUBLIC KEY-----", "")
      .replace("-----END PUBLIC KEY-----", "")
      .replace(/\n/g, "");
    const keyBytes = RSAUtils.decode(publicKey);
    const publicK = crypto.createPublicKey({
      key: keyBytes,
      format: "der",
      type: "spki",
    });

    const decryptedChunks: Uint8Array[] = [];
    let offset = 0;
    while (offset < encryptedData.length) {
      const chunkSize = Math.min(
        encryptedData.length - offset,
        RSAUtils.MAX_DECRYPT_BLOCK
      );
      const chunk = new Uint8Array(
        encryptedData.subarray(offset, offset + chunkSize)
      );
      const decryptedChunk = crypto.publicDecrypt(
        {
          key: publicK,
          padding: crypto.constants.RSA_PKCS1_PADDING,
        },
        chunk
      );
      decryptedChunks.push(new Uint8Array(decryptedChunk));
      offset += chunkSize;
    }
    return Buffer.concat(decryptedChunks);
  }

  /**
   * <p>
   * 公钥加密
   * </p>
   *
   * @param data      源数据
   * @param publicKey 公钥(BASE64编码)
   * @returns 加密后的数据
   */
  public static async encryptByPublicKey(
    data: Buffer,
    publicKey: string
  ): Promise<Buffer> {
    publicKey = publicKey
      .replace("-----BEGIN PUBLIC KEY-----", "")
      .replace("-----END PUBLIC KEY-----", "")
      .replace(/\n/g, "");
    const keyBytes = RSAUtils.decode(publicKey);
    const publicK = crypto.createPublicKey({
      key: keyBytes,
      format: "der",
      type: "spki",
    });

    const encryptedChunks: Uint8Array[] = [];
    let offset = 0;
    while (offset < data.length) {
      const chunkSize = Math.min(
        data.length - offset,
        RSAUtils.MAX_ENCRYPT_BLOCK
      );
      const chunk = new Uint8Array(data.subarray(offset, offset + chunkSize));
      const encryptedChunk = crypto.publicEncrypt(
        {
          key: publicK,
          padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        },
        chunk
      );
      encryptedChunks.push(new Uint8Array(encryptedChunk));
      offset += chunkSize;
    }
    return Buffer.concat(encryptedChunks);
  }

  /**
   * <p>
   * 私钥加密
   * </p>
   *
   * @param data       源数据
   * @param privateKey 私钥(BASE64编码)
   * @returns 加密后的数据
   */
  public static async encryptByPrivateKey(
    data: Buffer,
    privateKey: string
  ): Promise<Buffer> {
    privateKey = privateKey
      .replace("-----BEGIN PRIVATE KEY-----", "")
      .replace("-----END PRIVATE KEY-----", "")
      .replace(/\n/g, "");
    const keyBytes = RSAUtils.decode(privateKey);
    const privateK = crypto.createPrivateKey({
      key: keyBytes,
      format: "der",
      type: "pkcs8",
    });

    const encryptedChunks: Uint8Array[] = [];
    let offset = 0;
    while (offset < data.length) {
      const chunkSize = Math.min(
        data.length - offset,
        RSAUtils.MAX_ENCRYPT_BLOCK
      );
      const chunk = new Uint8Array(data.subarray(offset, offset + chunkSize));
      const encryptedChunk = crypto.privateEncrypt(
        {
          key: privateK,
          padding: crypto.constants.RSA_PKCS1_PADDING,
        },
        chunk
      );
      encryptedChunks.push(new Uint8Array(encryptedChunk));
      offset += chunkSize;
    }
    return Buffer.concat(encryptedChunks);
  }

  /**
   * <p>
   * 获取私钥
   * </p>
   *
   * @param keyMap 密钥对
   * @returns 私钥字符串
   */
  public static async getPrivateKey(keyMap: {
    [key: string]: string;
  }): Promise<string> {
    return keyMap[RSAUtils.PRIVATE_KEY];
  }

  /**
   * <p>
   * 获取公钥
   * </p>
   *
   * @param keyMap 密钥对
   * @returns 公钥字符串
   */
  public static async getPublicKey(keyMap: {
    [key: string]: string;
  }): Promise<string> {
    return keyMap[RSAUtils.PUBLIC_KEY];
  }

  /**
   * * 将 Base64 编码的私钥字符串转换为 PrivateKey 对象。
   *
   * @param key Base64 编码的私钥字符串
   * @returns 转换后的 PrivateKey 对象
   */
  public static getPrivateKeyObject(key: string): crypto.KeyObject {
    key = key
      .replace("-----BEGIN PRIVATE KEY-----", "")
      .replace("-----END PRIVATE KEY-----", "")
      .replace(/\n/g, "");
    const keyBytes = RSAUtils.decode(key);
    return crypto.createPrivateKey({
      key: keyBytes,
      format: "der",
      type: "pkcs8",
    });
  }

  /**
   * 将 Base64 编码的公钥字符串转换为 PublicKey 对象。
   *
   * @param key Base64 编码的公钥字符串
   * @returns 转换后的 PublicKey 对象
   */
  public static getPublicKeyObject(key: string): crypto.KeyObject {
    key = key
      .replace("-----BEGIN PUBLIC KEY-----", "")
      .replace("-----END PUBLIC KEY-----", "")
      .replace(/\n/g, "");
    const keyBytes = RSAUtils.decode(key);
    return crypto.createPublicKey({
      key: keyBytes,
      format: "der",
      type: "spki",
    });
  }
  /**
   * BASE64字符串解码为二进制数据
   * @param base64 BASE64编码字符串
   * @returns 解码后的二进制数据
   */
  public static decode(base64: string): Buffer {
    return Buffer.from(base64, "base64");
  }

  /**
   * 二进制数据编码为BASE64字符串
   * @param bytes 二进制数据
   * @returns 编码后的BASE64字符串
   */
  public static encode(bytes: Buffer): string {
    return bytes.toString("base64");
  }
}
